/*-------------------------------------------------------------------------
 *
 * fe-secure-openssl.c
 *	  OpenSSL 支持
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/interfaces/libpq/fe-secure-openssl.c
 *
 * NOTES
 *
 *	  我们在这里不提供信息回调（如 be-secure-openssl.c 中的 info_cb()），因为没有好的机制将此类信息显示给用户。
 *
 *-------------------------------------------------------------------------
 */

#include "postgres_fe.h"

#include <signal.h>
#include <fcntl.h>
#include <ctype.h>

#include "libpq-fe.h"
#include "fe-auth.h"
#include "fe-secure-common.h"
#include "libpq-int.h"

#ifdef WIN32
#include "win32.h"
#else
#include <sys/socket.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <arpa/inet.h>
#endif

#include <sys/stat.h>

#ifdef ENABLE_THREAD_SAFETY
#ifdef WIN32
#include "pthread-win32.h"
#else
#include <pthread.h>
#endif
#endif

/*
 * 这些与SSL相关的#includes必须在所有系统提供的头文件之后。
 * 这确保OpenSSL可以通过#undef'冲突的宏来处理与Windows的
 * <wincrypt.h>的冲突。 （我们不直接包含<wincrypt.h>，但其他一些Windows头文件确实包含。）
 */
#include "common/openssl.h"
#include <openssl/conf.h>
#ifdef USE_SSL_ENGINE
#include <openssl/engine.h>
#endif
#include <openssl/x509v3.h>


static int	fc_verify_cb(int fc_ok, X509_STORE_CTX *fc_ctx);
static int	fc_openssl_verify_peer_name_matches_certificate_name(PGconn *fc_conn,
															  ASN1_STRING *fc_name,
															  char **fc_store_name);
static int	fc_openssl_verify_peer_name_matches_certificate_ip(PGconn *fc_conn,
															ASN1_OCTET_STRING *fc_addr_entry,
															char **fc_store_name);
static void fc_destroy_ssl_system(void);
static int	fc_initialize_SSL(PGconn *fc_conn);
static PostgresPollingStatusType fc_open_client_SSL(PGconn *);
static char *fc_SSLerrmessage(unsigned long fc_ecode);
static void fc_SSLerrfree(char *fc_buf);
static int	fc_PQssl_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata);

static int	fc_my_sock_read(BIO *fc_h, char *fc_buf, int fc_size);
static int	fc_my_sock_write(BIO *fc_h, const char *fc_buf, int fc_size);
static BIO_METHOD *fc_my_BIO_s_socket(void);
static int	fc_my_SSL_set_fd(PGconn *fc_conn, int fc_fd);


static bool pq_init_ssl_lib = true;
static bool pq_init_crypto_lib = true;

static bool ssl_lib_initialized = false;

#ifdef ENABLE_THREAD_SAFETY
static long crypto_open_connections = 0;

static pthread_mutex_t ssl_config_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif							/* 启用线程安全 */

#ifdef FDDGM
static bool g_tlcp_enable = true;
#endif

static PQsslKeyPassHook_OpenSSL_type PQsslKeyPassHook = NULL;
static int	fc_ssl_protocol_version_to_openssl(const char *fc_protocol);

/* ------------------------------------------------------------ */
/*			 所有安全会话的共用过程			*/
/* ------------------------------------------------------------ */

void pgtls_init_library(bool fc_do_ssl, int fc_do_crypto)
{
#ifdef ENABLE_THREAD_SAFETY

	/*
	 * 在我们有开放连接的时候不允许更改标志，否则我们会
	 * 完全困惑。
	 */
	if (crypto_open_connections != 0)
		return;
#endif

	pq_init_ssl_lib = fc_do_ssl;
	pq_init_crypto_lib = fc_do_crypto;
}

PostgresPollingStatusType pgtls_open_client(PGconn *fc_conn)
{
	/* 第一次执行？ */
	if (fc_conn->ssl == NULL)
	{
		/*
		 * 创建一个特定于连接的SSL对象，并加载客户端
		 * 证书、私钥和受信任的CA证书。
		 */
		if (fc_initialize_SSL(fc_conn) != 0)
		{
			/* initialize_SSL已经在conn->errorMessage中放置了一个消息 */
			pgtls_close(fc_conn);
			return PGRES_POLLING_FAILED;
		}
	}

	/* 开始或继续实际的握手 */
	return fc_open_client_SSL(fc_conn);
}

ssize_t pgtls_read(PGconn *fc_conn, void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;
	int			fc_result_errno = 0;
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];
	int			fc_err;
	unsigned long fc_ecode;

rloop:

	/*
	 * 通过清除线程的OpenSSL错误队列来准备调用SSL_get_error()。
	 * 一般来说，在尝试TLS/SSL I/O操作之前，当前线程的错误队列必须为空，
	 * 否则SSL_get_error()将无法可靠工作。由于存在其他OpenSSL
	 * 客户端在同一线程中运行但不在我们控制下的情况，它们可能未能
	 * 自行调用ERR_get_error()（在它们自己的I/O操作之后），
	 * 所以现在主动清除每个线程的错误队列。
	 */
	SOCK_ERRNO_SET(0);
	ERR_clear_error();
	fc_n = SSL_read(fc_conn->ssl, fc_ptr, fc_len);
	fc_err = SSL_get_error(fc_conn->ssl, fc_n);

	/*
	 * OpenSSL的其他客户端可能会未能调用ERR_get_error()，但我们
	 * 总是会调用，以防止未防御性调用ERR_clear_error()的OpenSSL客户端
	 * 遇到问题。确保现在调用。SSL_get_error()依赖于OpenSSL每线程错误
	 * 队列的完整性，因此这是调用ERR_get_error()的最早时间点。
	 */
	fc_ecode = (fc_err != SSL_ERROR_NONE || fc_n < 0) ? ERR_get_error() : 0;
	switch (fc_err)
	{
		case SSL_ERROR_NONE:
			if (fc_n < 0)
			{
				/* 不应发生，因此我们不翻译消息 */
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 "SSL_read failed but did not provide error information\n");
				/* 假设连接已断开 */
				fc_result_errno = ECONNRESET;
			}
			break;
		case SSL_ERROR_WANT_READ:
			fc_n = 0;
			break;
		case SSL_ERROR_WANT_WRITE:

			/*
			 * 在这里返回0会导致调用者等待读就绪，
			 * 这不正确，因为SSL想要的是等待
			 * 写就绪。前者可能会让我们陷入无限的
			 * 等待，因此不要冒这个风险；改为忙循环。
			 */
			goto rloop;
		case SSL_ERROR_SYSCALL:
			if (fc_n < 0 && SOCK_ERRNO != 0)
			{
				fc_result_errno = SOCK_ERRNO;
				if (fc_result_errno == EPIPE ||
					fc_result_errno == ECONNRESET)
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("server closed the connection unexpectedly\n"
													   "\tThis probably means the server terminated abnormally\n"
													   "\tbefore or while processing the request.\n"));
				else
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("SSL SYSCALL error: %s\n"),
									  SOCK_STRERROR(fc_result_errno,
													fc_sebuf, sizeof(fc_sebuf)));
			}
			else
			{
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
				/* 假设连接已断开 */
				fc_result_errno = ECONNRESET;
				fc_n = -1;
			}
			break;
		case SSL_ERROR_SSL:
			{
				char	   *fc_errm = fc_SSLerrmessage(fc_ecode);

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("SSL error: %s\n"), fc_errm);
				fc_SSLerrfree(fc_errm);
				/* 假设连接已断开 */
				fc_result_errno = ECONNRESET;
				fc_n = -1;
				break;
			}
		case SSL_ERROR_ZERO_RETURN:

			/*
			 * 根据OpenSSL文档，此错误代码仅在
			 * 干净的连接关闭时返回，因此我们不应将其报告为
			 * 服务器崩溃。
			 */
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("SSL connection has been closed unexpectedly\n"));
			fc_result_errno = ECONNRESET;
			fc_n = -1;
			break;
		default:
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("unrecognized SSL error code: %d\n"),
							  fc_err);
			/* 假设连接已断开 */
			fc_result_errno = ECONNRESET;
			fc_n = -1;
			break;
	}

	/* 确保我们将预期的errno返回给调用者 */
	SOCK_ERRNO_SET(fc_result_errno);

	return fc_n;
}

bool pgtls_read_pending(PGconn *fc_conn)
{
	return SSL_pending(fc_conn->ssl) > 0;
}

ssize_t pgtls_write(PGconn *fc_conn, const void *fc_ptr, size_t fc_len)
{
	ssize_t		fc_n;
	int			fc_result_errno = 0;
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];
	int			fc_err;
	unsigned long fc_ecode;

	SOCK_ERRNO_SET(0);
	ERR_clear_error();
	fc_n = SSL_write(fc_conn->ssl, fc_ptr, fc_len);
	fc_err = SSL_get_error(fc_conn->ssl, fc_n);
	fc_ecode = (fc_err != SSL_ERROR_NONE || fc_n < 0) ? ERR_get_error() : 0;
	switch (fc_err)
	{
		case SSL_ERROR_NONE:
			if (fc_n < 0)
			{
				/* 不应发生，因此我们不翻译消息 */
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 "SSL_write failed but did not provide error information\n");
				/* 假设连接已断开 */
				fc_result_errno = ECONNRESET;
			}
			break;
		case SSL_ERROR_WANT_READ:

			/*
			 * 在这里返回0会导致调用者等待写就绪，这
			 * 实际上并不是正确的做法，但这是我们能做的最好选择。
			 */
			fc_n = 0;
			break;
		case SSL_ERROR_WANT_WRITE:
			fc_n = 0;
			break;
		case SSL_ERROR_SYSCALL:

			/*
			 * 如果errno仍为零，则假设这是读取EOF情况，
			 * 并报告EOF。 （这似乎是可能的，因为SSL_write也
			 * 可以执行读取。）
			 */
			if (fc_n < 0 && SOCK_ERRNO != 0)
			{
				fc_result_errno = SOCK_ERRNO;
				if (fc_result_errno == EPIPE || fc_result_errno == ECONNRESET)
					appendPQExpBufferStr(&fc_conn->errorMessage,
										 libpq_gettext("server closed the connection unexpectedly\n"
													   "\tThis probably means the server terminated abnormally\n"
													   "\tbefore or while processing the request.\n"));
				else
					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("SSL SYSCALL error: %s\n"),
									  SOCK_STRERROR(fc_result_errno,
													fc_sebuf, sizeof(fc_sebuf)));
			}
			else
			{
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
				/* 假设连接已断开 */
				fc_result_errno = ECONNRESET;
				fc_n = -1;
			}
			break;
		case SSL_ERROR_SSL:
			{
				char	   *fc_errm = fc_SSLerrmessage(fc_ecode);

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("SSL error: %s\n"), fc_errm);
				fc_SSLerrfree(fc_errm);
				/* 假设连接已断开 */
				fc_result_errno = ECONNRESET;
				fc_n = -1;
				break;
			}
		case SSL_ERROR_ZERO_RETURN:

			/*
			 * 根据OpenSSL文档，此错误代码仅在
			 * 干净的连接关闭时返回，因此我们不应将其报告为
			 * 服务器崩溃。
			 */
			appendPQExpBufferStr(&fc_conn->errorMessage,
								 libpq_gettext("SSL connection has been closed unexpectedly\n"));
			fc_result_errno = ECONNRESET;
			fc_n = -1;
			break;
		default:
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("unrecognized SSL error code: %d\n"),
							  fc_err);
			/* 假设连接已断开 */
			fc_result_errno = ECONNRESET;
			fc_n = -1;
			break;
	}

	/* 确保我们将预期的errno返回给调用者 */
	SOCK_ERRNO_SET(fc_result_errno);

	return fc_n;
}

#if defined(HAVE_X509_GET_SIGNATURE_NID) || defined(HAVE_X509_GET_SIGNATURE_INFO)
char * pgtls_get_peer_certificate_hash(PGconn *fc_conn, size_t *fc_len)
{
	X509	   *fc_peer_cert;
	const EVP_MD *fc_algo_type;
	unsigned char fc_hash[EVP_MAX_MD_SIZE];	/* SHA-512 的大小 */
	unsigned int fc_hash_size;
	int			fc_algo_nid;
	char	   *fc_cert_hash;

	*fc_len = 0;

	if (!fc_conn->peer)
		return NULL;

	fc_peer_cert = fc_conn->peer;

	/*
	 * 获取证书的签名算法，以确定结果使用的哈希
	 * 算法。优先使用 X509_get_signature_info()，
	 * 该函数在 OpenSSL 1.1.1 中引入，可以处理 RSA-PSS 签名。
	 */
#if HAVE_X509_GET_SIGNATURE_INFO
	if (!X509_get_signature_info(fc_peer_cert, &fc_algo_nid, NULL, NULL, NULL))
#else
	if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
							 &algo_nid, NULL))
#endif
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("could not determine server certificate signature algorithm\n"));
		return NULL;
	}

	/*
	 * 如果 TLS 服务器的证书签名算法为 MD5 或 SHA-1，根据 RFC 5929
	 * (https://tools.ietf.org/html/rfc5929#section-4.1) 需要使用 SHA-256 对
	 * 证书字节进行哈希处理。如果使用其他算法，则使用与签名算法相同的哈希。
	 */
	switch (fc_algo_nid)
	{
		case NID_md5:
		case NID_sha1:
			fc_algo_type = EVP_sha256();
			break;
		default:
			fc_algo_type = EVP_get_digestbynid(fc_algo_nid);
			if (fc_algo_type == NULL)
			{
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not find digest for NID %s\n"),
								  OBJ_nid2sn(fc_algo_nid));
				return NULL;
			}
			break;
	}

	if (!X509_digest(fc_peer_cert, fc_algo_type, fc_hash, &fc_hash_size))
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("could not generate peer certificate hash\n"));
		return NULL;
	}

	/* 保存结果 */
	fc_cert_hash = malloc(fc_hash_size);
	if (fc_cert_hash == NULL)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("out of memory\n"));
		return NULL;
	}
	memcpy(fc_cert_hash, fc_hash, fc_hash_size);
	*fc_len = fc_hash_size;

	return fc_cert_hash;
}
#endif							/* HAVE_X509_GET_SIGNATURE_NID */

/* ------------------------------------------------------------ */
/*						OpenSSL 特定代码					*/
/* ------------------------------------------------------------ */

/*
 * 证书验证回调
 *
 * 该回调允许我们记录验证过程中出现的中间问题，但没有
 * 清晰的方法来获取我们的 PGconn * 结构。 因此我们无法记录任何内容！
 *
 * 该回调还允许我们覆盖默认的接受
 * 标准（例如，接受自签名或过期证书），但
 * 目前我们接受默认检查。
 */
static int fc_verify_cb(int fc_ok, X509_STORE_CTX *fc_ctx)
{
	return fc_ok;
}


/*
 * OpenSSL 特定的包装器，用于
 * pq_verify_peer_name_matches_certificate_name()，将 ASN1_STRING
 * 转换为普通 C 字符串。
 */
static int fc_openssl_verify_peer_name_matches_certificate_name(PGconn *fc_conn, ASN1_STRING *fc_name_entry,
												  char **fc_store_name)
{
	int			fc_len;
	const unsigned char *fc_namedata;

	/* 不应发生... */
	if (fc_name_entry == NULL)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("SSL certificate's name entry is missing\n"));
		return -1;
	}

	/*
	 * GEN_DNS 只能是 IA5String，相当于 US ASCII。
	 */
#ifdef HAVE_ASN1_STRING_GET0_DATA
	fc_namedata = ASN1_STRING_get0_data(fc_name_entry);
#else
	fc_namedata = ASN1_STRING_data(fc_name_entry);
#endif
	fc_len = ASN1_STRING_length(fc_name_entry);

	/* 从无符号转换为普通字符是可以的，因为它都是 ASCII。 */
	return pq_verify_peer_name_matches_certificate_name(fc_conn, (const char *) fc_namedata, fc_len, fc_store_name);
}

/*
 * OpenSSL 特定的包装器，用于
 * pq_verify_peer_name_matches_certificate_ip()，将
 * ASN1_OCTET_STRING 转换为普通 C 字符串。
 */
static int fc_openssl_verify_peer_name_matches_certificate_ip(PGconn *fc_conn,
												ASN1_OCTET_STRING *fc_addr_entry,
												char **fc_store_name)
{
	int			fc_len;
	const unsigned char *fc_addrdata;

	/* 不应发生... */
	if (fc_addr_entry == NULL)
	{
		appendPQExpBufferStr(&fc_conn->errorMessage,
							 libpq_gettext("SSL certificate's address entry is missing\n"));
		return -1;
	}

	/*
	 * GEN_IPADD 是一个 OCTET STRING，其中包含网络字节顺序的 IP 地址。
	 */
#ifdef HAVE_ASN1_STRING_GET0_DATA
	fc_addrdata = ASN1_STRING_get0_data(fc_addr_entry);
#else
	fc_addrdata = ASN1_STRING_data(fc_addr_entry);
#endif
	fc_len = ASN1_STRING_length(fc_addr_entry);

	return pq_verify_peer_name_matches_certificate_ip(fc_conn, fc_addrdata, fc_len, fc_store_name);
}

static bool fc_is_ip_address(const char *fc_host)
{
	struct in_addr fc_dummy4;
#ifdef HAVE_INET_PTON
	struct in6_addr fc_dummy6;
#endif

	return inet_aton(fc_host, &fc_dummy4)
#ifdef HAVE_INET_PTON
		|| (inet_pton(AF_INET6, fc_host, &fc_dummy6) == 1)
#endif
		;
}

/*
 * 验证服务器证书是否与我们连接的主机名匹配。
 *
 * 考虑证书的通用名称和主题备用名称。
 */
int pgtls_verify_peer_name_matches_certificate_guts(PGconn *fc_conn,
												int *fc_names_examined,
												char **fc_first_name)
{
	STACK_OF(GENERAL_NAME) * fc_peer_san;
	int			fc_i;
	int			fc_rc = 0;
	char	   *fc_host = fc_conn->connhost[fc_conn->whichhost].host;
	int			fc_host_type;
	bool		fc_check_cn = true;

	Assert(fc_host && fc_host[0]);	/* 应由调用者保证 */

	/*
	 * 我们在这里尝试匹配 NSS 的行为，这略微偏离了规范，但似乎更符合直觉：
	 *
	 * 如果 connhost 包含一个 DNS 名称，并且证书的 SANs 包含任何 dNSName 条目，
	 * 那么我们将完全忽略主题公用名；否则，我们将退回到检查 CN。（这种行为符合
	 * RFC。）
	 *
	 * 如果 connhost 包含一个 IP 地址，并且 SANs 包含 iPAddress 条目，我们同样
	 * 忽略 CN。否则，我们允许 CN 匹配，即使 SANs 中有 dNSName。（RFC 6125 禁止
	 * 这样做：“如果呈现的标识符包括 DNS-ID、SRV-ID、URI-ID 或任何
	 * 客户端支持的应用程序特定标识符类型，则客户端不得寻求与 CN-ID 的参考标识符
	 * 匹配。”）
	 *
	 * 注意：libpq 的早期版本根本不考虑 iPAddress 条目，因此这种新行为可能会
	 * 破坏在主题 CN 和 SANs 中有不同 IP 地址的证书。
	 */
	if (fc_is_ip_address(fc_host))
		fc_host_type = GEN_IPADD;
	else
		fc_host_type = GEN_DNS;

	/*
	 * 首先，从证书中获取主题备用名称 (SANs)，并将其与最初给定的主机名进行比较。
	 */
	fc_peer_san = (STACK_OF(GENERAL_NAME) *)
		X509_get_ext_d2i(fc_conn->peer, NID_subject_alt_name, NULL, NULL);

	if (fc_peer_san)
	{
		int			fc_san_len = sk_GENERAL_NAME_num(fc_peer_san);

		for (fc_i = 0; fc_i < fc_san_len; fc_i++)
		{
			const GENERAL_NAME *fc_name = sk_GENERAL_NAME_value(fc_peer_san, fc_i);
			char	   *fc_alt_name = NULL;

			if (fc_name->type == fc_host_type)
			{
				/*
				 * 这个 SAN 与我们的主机名是同一类型（IP 或 DNS），
				 * 所以不允许退回检查 CN。
				 */
				fc_check_cn = false;
			}

			if (fc_name->type == GEN_DNS)
			{
				(*fc_names_examined)++;
				fc_rc = fc_openssl_verify_peer_name_matches_certificate_name(fc_conn,
																	   fc_name->d.dNSName,
																	   &fc_alt_name);
			}
			else if (fc_name->type == GEN_IPADD)
			{
				(*fc_names_examined)++;
				fc_rc = fc_openssl_verify_peer_name_matches_certificate_ip(fc_conn,
																	 fc_name->d.iPAddress,
																	 &fc_alt_name);
			}

			if (fc_alt_name)
			{
				if (!*fc_first_name)
					*fc_first_name = fc_alt_name;
				else
					free(fc_alt_name);
			}

			if (fc_rc != 0)
			{
				/*
				 * 我们要么遇到错误，要么匹配，无论哪种情况我们都不应该
				 * 退回到 CN。
				 */
				fc_check_cn = false;
				break;
			}
		}
		sk_GENERAL_NAME_pop_free(fc_peer_san, GENERAL_NAME_free);
	}

	/*
	 * 如果没有匹配类型的 subjectAltName 扩展，检查 Common Name。
	 *
	 * （根据 RFC 2818 和 RFC 6125，如果存在类型为 dNSName 的 subjectAltName
	 * 扩展，则必须忽略 CN。如果主机是 IP 地址，我们破坏这个规则；见上面的注释。）
	 */
	if (fc_check_cn)
	{
		X509_NAME  *fc_subject_name;

		fc_subject_name = X509_get_subject_name(fc_conn->peer);
		if (fc_subject_name != NULL)
		{
			int			fc_cn_index;

			fc_cn_index = X509_NAME_get_index_by_NID(fc_subject_name,
												  NID_commonName, -1);
			if (fc_cn_index >= 0)
			{
				char	   *fc_common_name = NULL;

				(*fc_names_examined)++;
				fc_rc = fc_openssl_verify_peer_name_matches_certificate_name(fc_conn,
																	   X509_NAME_ENTRY_get_data(X509_NAME_get_entry(fc_subject_name, fc_cn_index)),
																	   &fc_common_name);

				if (fc_common_name)
				{
					if (!*fc_first_name)
						*fc_first_name = fc_common_name;
					else
						free(fc_common_name);
				}
			}
		}
	}

	return fc_rc;
}

#if defined(ENABLE_THREAD_SAFETY) && defined(HAVE_CRYPTO_LOCK)
/*
 *	OpenSSL 内部锁定的回调函数。（OpenSSL 1.1.0 自行处理锁定，并不再需要这些。
 *	CRYPTO_lock() 函数在 1.1.0 中被移除，当时回调被宣布为过时，因此我们假设
 *	如果 CRYPTO_lock() 存在，则回调仍然是必需的。）
 */

static unsigned long
fc_pq_threadidcallback(void)
{
	/*
	 * 这并不符合标准。pthread_self() 返回 pthread_t，不应该被转换为
	 * unsigned long，但 CRYPTO_set_id_callback 需要这样做，所以我们必须这样做。
	 */
	return (unsigned long) pthread_self();
}

static pthread_mutex_t *pq_lockarray;

static void fc_pq_lockingcallback(int fc_mode, int fc_n, const char *fc_file, int fc_line)
{
	/*
	 * 没有办法报告互斥锁原语失败，所以我们在开发构建中只进行断言，
	 * 否则忽略任何错误。幸运的是，这在现代 OpenSSL 中都是过时的。
	 */
	if (fc_mode & CRYPTO_LOCK)
	{
		if (pthread_mutex_lock(&pq_lockarray[fc_n]))
			Assert(false);
	}
	else
	{
		if (pthread_mutex_unlock(&pq_lockarray[fc_n]))
			Assert(false);
	}
}
#endif							/* ENABLE_THREAD_SAFETY && HAVE_CRYPTO_LOCK */

/*
 * 初始化 SSL 库。
 *
 * 在线程安全模式下，这包括设置 libcrypto 回调函数以进行线程锁定。
 *
 * 如果调用者通过 PQinitOpenSSL 告诉我们他正在处理 libcrypto，
 * 我们期望已经设置好回调，并且不会尝试覆盖它。
 */
int pgtls_init(PGconn *fc_conn, bool fc_do_ssl, bool fc_do_crypto)
{
#ifdef ENABLE_THREAD_SAFETY
	if (pthread_mutex_lock(&ssl_config_mutex))
		return -1;

#ifdef HAVE_CRYPTO_LOCK
	if (pq_init_crypto_lib)
	{
		/*
		 * 如果需要，设置一个数组以保存 libcrypto 的锁。
		 * libcrypto 会告诉我们这个数组的大小。
		 */
		if (pq_lockarray == NULL)
		{
			int			fc_i;

			pq_lockarray = malloc(sizeof(pthread_mutex_t) * CRYPTO_num_locks());
			if (!pq_lockarray)
			{
				pthread_mutex_unlock(&ssl_config_mutex);
				return -1;
			}
			for (fc_i = 0; fc_i < CRYPTO_num_locks(); fc_i++)
			{
				if (pthread_mutex_init(&pq_lockarray[fc_i], NULL))
				{
					free(pq_lockarray);
					pq_lockarray = NULL;
					pthread_mutex_unlock(&ssl_config_mutex);
					return -1;
				}
			}
		}

		if (fc_do_crypto && !fc_conn->crypto_loaded)
		{
			if (crypto_open_connections++ == 0)
			{
				/*
				 * 这些只在多线程 libcrypto 应用程序中需要，但确保我们不会覆盖它们，如果它们已经设置。
				 */
				if (CRYPTO_get_id_callback() == NULL)
					CRYPTO_set_id_callback(fc_pq_threadidcallback);
				if (CRYPTO_get_locking_callback() == NULL)
					CRYPTO_set_locking_callback(pq_lockingcallback);
			}

			fc_conn->crypto_loaded = true;
		}
	}
#endif							/* HAVE_CRYPTO_LOCK */
#endif							/* 启用线程安全 */

	if (!ssl_lib_initialized && fc_do_ssl)
	{
		if (pq_init_ssl_lib)
		{
#ifdef HAVE_OPENSSL_INIT_SSL
			OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL);
#else
			OPENSSL_config(NULL);
			SSL_library_init();
			SSL_load_error_strings();
#endif
		}
		ssl_lib_initialized = true;
	}

#ifdef ENABLE_THREAD_SAFETY
	pthread_mutex_unlock(&ssl_config_mutex);
#endif
	return 0;
}

/*
 *	这个函数是必需的，因为如果从应用程序中卸载了 libpq 库，当 libcrypto 被系统的其他部分使用时，回调函数将不再存在。因此，
 *	我们在最后一个 libpq 连接关闭时注销回调函数。（如果我们有 OpenSSL 回调，情况也是如此。）
 *
 *	只有在我们以线程安全模式编译时才会设置回调，因此
 *	我们只需要在这种情况下删除它们。它们在 OpenSSL 1.1.0 中也不再需要。
 */
static void fc_destroy_ssl_system(void)
{
#if defined(ENABLE_THREAD_SAFETY) && defined(HAVE_CRYPTO_LOCK)
	if (pthread_mutex_lock(&ssl_config_mutex))
		return;

	if (pq_init_crypto_lib && crypto_open_connections > 0)
		--crypto_open_connections;

	if (pq_init_crypto_lib && crypto_open_connections == 0)
	{
		/*
		 * 没有连接了，注销 libcrypto 回调，如果没有人在此期间
		 * 注册不同的回调。
		 */
		if (CRYPTO_get_locking_callback() == fc_pq_lockingcallback)
			CRYPTO_set_locking_callback(NULL);
		if (CRYPTO_get_id_callback() == fc_pq_threadidcallback)
			CRYPTO_set_id_callback(NULL);

		/*
		 * 我们不释放锁数组。如果我们在此进程中获得另一个连接，
		 * 我们将只需与现有的互斥锁重用它们。
		 *
		 * 这意味着我们在重复加载/卸载库时会泄漏一点内存。
		 */
	}

	pthread_mutex_unlock(&ssl_config_mutex);
#endif
}

#ifdef FDDGM
/* 只有 TLCP 模式才调用该函数, 读取客户端证书文件 */
static int fc_load_certificate_file(PGconn *fc_conn, bool	 fc_have_homedir, char	fc_homedir[MAXPGPATH], char * fc_file_name, SSL_CTX  *fc_SSL_context, int fc_usage, bool	*fc_have_cert)
{
	struct stat fc_buf;
	char		fc_fnbuf[MAXPGPATH] = {0};
	char		fc_sebuf[PG_STRERROR_R_BUFLEN] = {0};
	char * fc_conn_cert_file = ( fc_usage == SSL_USAGE_SIG )? fc_conn->sslcert : fc_conn->sslenccert;

	/* 读取客户端证书文件 */
	if (fc_conn_cert_file && strlen(fc_conn_cert_file) > 0)
		strlcpy(fc_fnbuf, fc_conn_cert_file, sizeof(fc_fnbuf));
	else if (fc_have_homedir)
		snprintf(fc_fnbuf, sizeof(fc_fnbuf), "%s/%s", fc_homedir, fc_file_name);
	else
		fc_fnbuf[0] = '\0';

	if (fc_fnbuf[0] == '\0')
	{
		/* 没有家目录，继续没有客户端证书 */
		*fc_have_cert = false;
	}
	else if (stat(fc_fnbuf, &fc_buf) != 0)
	{
		/*
		 * 如果文件不存在，就继续没有客户端证书；服务器
		 * 可能会接受连接，也可能不会。然而，任何其他错误
		 * 都是投诉的理由。
		 */
		if (errno != ENOENT && errno != ENOTDIR)
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not open certificate file \"%s\": %s\n"),
							  fc_fnbuf, strerror_r(errno, fc_sebuf, sizeof(fc_sebuf)));
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}
		*fc_have_cert = false;
	}
	else
	{
		/*
		 * 证书文件存在，因此加载它。由于 OpenSSL 不提供
		 * "SSL_use_certificate_chain_file" 的等效方法，我们必须将其加载
		 * 到 SSL 上下文中，而不是 SSL 对象中。
		 */
		 //tlcp 目前不支持包括中间证书的信任链证书读取
		if (SSL_CTX_use_gm_certificate_file(fc_SSL_context, fc_fnbuf, SSL_FILETYPE_PEM, fc_usage) != 1)
		{
			char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not read certificate file \"%s\": %s\n"),
							  fc_fnbuf, fc_err);
			fc_SSLerrfree(fc_err);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}

		/* 需要加载相关的私钥 */
		*fc_have_cert = true;
	}

	return 0;
}


/* 只有 TLCP 模式才调用该函数, 读取客户端私钥文件 */
#define TLCP_MAX_PROMPT 80 //openssl 中保持密钥提示变量 prompt_string 的最大长度
static int fc_load_privatekey_file(PGconn *fc_conn, bool	 fc_have_homedir, char	fc_homedir[MAXPGPATH], char * fc_file_name,int fc_usage, bool	fc_have_cert)
{
	struct stat fc_buf;
	char		fc_fnbuf[MAXPGPATH];
	char        fc_prompt_string[TLCP_MAX_PROMPT] = {0};
	EVP_PKEY   *fc_pkey = NULL;
	char * fc_conn_key_file = ( fc_usage == SSL_USAGE_SIG )? fc_conn->sslkey : fc_conn->sslenckey;
	snprintf(fc_prompt_string, sizeof(fc_prompt_string), "Enter pass phrase for %s:",  ( fc_usage == SSL_USAGE_SIG )?"postgresql.key":"encpostgresql.key");

	/*
	 * 读取 SSL 密钥。如果指定了密钥，如果存在冒号，则将其视为 engine:key
	 * 组合 - 我们不支持名称中包含冒号的文件。例外是如果第二个字符是冒号，
	 * 在这种情况下它可以是带有驱动器说明的 Windows 文件名。
	 */
	if (fc_have_cert && fc_conn_key_file && strlen(fc_conn_key_file) > 0)
	{
#ifdef USE_SSL_ENGINE
		if (strchr(fc_conn_key_file, ':')
#ifdef WIN32
			&& fc_conn_key_file[1] != ':'
#endif
			)
		{
			/* 冒号，但不是在第二个字符中，视为 engine:key */
			char	   *fc_engine_str = strdup(fc_conn_key_file);
			char	   *fc_engine_colon;

			if (fc_engine_str == NULL)
			{
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("out of memory\n"));
				return -1;
			}			
			
			EVP_set_pw_prompt(fc_prompt_string);

			/* 不能返回 NULL，因为我们已经在 strdup 之前检查过 */
			fc_engine_colon = strchr(fc_engine_str, ':');

			*fc_engine_colon = '\0';	/* engine_str 现在有引擎名称 */
			fc_engine_colon++;		/* engine_colon 现在有密钥名称 */

			fc_conn->engine = ENGINE_by_id(fc_engine_str);
			if (fc_conn->engine == NULL)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not load SSL engine \"%s\": %s\n"),
								  fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				free(fc_engine_str);
				return -1;
			}

			if (ENGINE_init(fc_conn->engine) == 0)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
								  fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				ENGINE_free(fc_conn->engine);
				fc_conn->engine = NULL;
				free(fc_engine_str);
				return -1;
			}
			
			fc_pkey = ENGINE_load_private_key(fc_conn->engine, fc_engine_colon,
										   NULL, NULL);
			if (fc_pkey == NULL)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
								  fc_engine_colon, fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				ENGINE_finish(fc_conn->engine);
				ENGINE_free(fc_conn->engine);
				fc_conn->engine = NULL;
				free(fc_engine_str);
				return -1;
			}
			/* 使用国密接口读取私钥文件 */
			if (SSL_use_gm_PrivateKey(fc_conn->ssl, fc_pkey,fc_usage) != 1)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
								  fc_engine_colon, fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				ENGINE_finish(fc_conn->engine);
				ENGINE_free(fc_conn->engine);
				fc_conn->engine = NULL;
				free(fc_engine_str);
				return -1;
			}

			free(fc_engine_str);

			fc_fnbuf[0] = '\0';	/* 表示我们不打算从一个
								 * 文件中加载 */
		}
		else
#endif							/* USE_SSL_ENGINE */
		{
			/* PGSSLKEY 不是一个引擎，将其视为文件名 */
			strlcpy(fc_fnbuf, fc_conn_key_file, sizeof(fc_fnbuf));
		}
	}
	else if (fc_have_homedir)
	{
		/* 未指定 PGSSLKEY，加载默认文件 */
		snprintf(fc_fnbuf, sizeof(fc_fnbuf), "%s/%s", fc_homedir, fc_file_name);
	}
	else
		fc_fnbuf[0] = '\0';

	if (fc_have_cert && fc_fnbuf[0] != '\0')
	{
		/* 从文件读取客户端密钥 */

		if (stat(fc_fnbuf, &fc_buf) != 0)
		{
			if (errno == ENOENT)
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("certificate present, but not private key file \"%s\"\n"),
								  fc_fnbuf);
			else
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not stat private key file \"%s\": %m\n"),
								  fc_fnbuf);
			return -1;
		}

		/* 密钥文件必须是常规文件 */
		if (!S_ISREG(fc_buf.st_mode))
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("private key file \"%s\" is not a regular file\n"),
							  fc_fnbuf);
			return -1;
		}

		/*
		 * 拒绝加载所有人可读的密钥文件。我们接受根拥有
		 * 的文件，模式为0640或更小，以便我们能够访问系统范围的
		 * 证书，前提是我们拥有允许我们读取它们的附加组成员资格。
		 * 对于非根拥有的文件，需要模式为0600或更小。我们不需要
		 * 精确检查文件的拥有权；如果我们能够读取文件，尽管它
		 * 拥有如此限制的权限，它一定具有正确的拥有权。
		 *
		 * 注意：在收紧这些规则时要非常小心。一些人
		 * 期望，例如，作为根用户运行的客户端进程应该能够使用
		 * 非根拥有的密钥文件。
		 *
		 * 请注意，在
		 * src/backend/libpq/be-secure-common.c 中执行了大致类似的检查，
		 * 因此在这里的任何更改也可能需要在那里进行。然而，这段代码
		 * 处理当前用户 == root 的情况，而那段代码没有。
		 *
		 * 理想情况下，我们将在Windows上执行类似的权限检查，
		 * 但目前尚不清楚这将如何工作，因为Unix风格的权限可能
		 * 不可用。
		 */
#if !defined(WIN32) && !defined(__CYGWIN__)
		if (fc_buf.st_uid == 0 ?
			fc_buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO) :
			fc_buf.st_mode & (S_IRWXG | S_IRWXO))
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("private key file \"%s\" has group or world access; file must have permissions u=rw (0600) or less if owned by the current user, or permissions u=rw,g=r (0640) or less if owned by root\n"),
							  fc_fnbuf);
			return -1;
		}
#endif
		EVP_set_pw_prompt(fc_prompt_string);

		/* 使用国密接口读取私钥文件 */
		if (SSL_use_gm_PrivateKey_file(fc_conn->ssl, fc_fnbuf, SSL_FILETYPE_PEM, fc_usage) != 1)
		{
			char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

			/*
			 * 我们将尝试以DER（二进制ASN.1）格式加载文件，如果
			 * 这也失败了，则报告原始错误。这可能会掩盖
			 * DER格式证书存在问题的情况，但我们必须复制
			 * openssl的格式检测，以比这更智能。我们不能仅仅
			 * 通过探测前导-----BEGIN，因为PEM可以具有前导的不匹配
			 * 行和空白。OpenSSL不公开其get_name(...)，并且它的
			 * PEM例程在失败模式上没有足够的区分度，让我们能够
			 * 区分“不是PEM，尝试DER”和“密码错误”。
			 */
			 /* 使用国密接口读取私钥文件 */
			if (SSL_use_gm_PrivateKey_file(fc_conn->ssl, fc_fnbuf, SSL_FILETYPE_ASN1, fc_usage) != 1)
			{
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not load private key file \"%s\": %s\n"),
								  fc_fnbuf, fc_err);
				fc_SSLerrfree(fc_err);
				return -1;
			}

			fc_SSLerrfree(fc_err);
		}
	}

	return 0;
}

#endif

/*
 *	为每个连接创建SSL对象，并加载客户端证书、
 *	私钥和受信任的CA证书。
 *
 *	如果成功则返回0，失败则返回-1（并在conn->errorMessage中
 *	提供消息）。
 */
static int fc_initialize_SSL(PGconn *fc_conn)
{
	SSL_CTX    *fc_SSL_context;
	struct stat fc_buf;
	char		fc_homedir[MAXPGPATH];
	char		fc_fnbuf[MAXPGPATH];
	char		fc_sebuf[PG_STRERROR_R_BUFLEN];
	bool		fc_have_homedir;
	bool		fc_have_cert;
#ifdef FDDGM
	bool		fc_have_enccert;//是否有 TLCP 加密证书
#endif
	bool		fc_have_rootcert;

#ifdef FDDGM
		/* 检查是否存在 TLCP加密证书，如果存在，则是走TLCP认证 */
		if (fc_conn->sslenccert && strlen(fc_conn->sslenccert) > 0)
			strlcpy(fc_fnbuf, fc_conn->sslenccert, sizeof(fc_fnbuf));
		else if (pqGetHomeDirectory(fc_homedir, sizeof(fc_homedir)))
			snprintf(fc_fnbuf, sizeof(fc_fnbuf), "%s/%s", fc_homedir, USER_ENC_CERT_FILE);
		else
			fc_fnbuf[0] = '\0';
	
		if (fc_fnbuf[0] != '\0' && stat(fc_fnbuf, &fc_buf) == 0)
		{		
			g_tlcp_enable = true;
		}
		else
		{		
			g_tlcp_enable = false;
		}
#endif
	/*
	 * 如果任何相关参数的默认值未被覆盖，我们需要主目录。
	 * 如果pqGetHomeDirectory失败，就像找不到任何文件一样处理。
	 */
	if (!(fc_conn->sslcert && strlen(fc_conn->sslcert) > 0) ||
		!(fc_conn->sslkey && strlen(fc_conn->sslkey) > 0) ||
		!(fc_conn->sslrootcert && strlen(fc_conn->sslrootcert) > 0) ||
#ifdef FDDGM
		!(g_tlcp_enable && fc_conn->sslenckey && strlen(fc_conn->sslenckey) > 0) ||
		!(g_tlcp_enable && fc_conn->sslenccert && strlen(fc_conn->sslenccert) > 0) ||
#endif
		!((fc_conn->sslcrl && strlen(fc_conn->sslcrl) > 0) ||
		  (fc_conn->sslcrldir && strlen(fc_conn->sslcrldir) > 0)))
		fc_have_homedir = pqGetHomeDirectory(fc_homedir, sizeof(fc_homedir));
	else						/* 不需要它 */
		fc_have_homedir = false;

	/*
	 * 创建一个新的SSL_CTX对象。
	 *
	 * 我们过去在所有连接之间共享一个SSL_CTX，但如果连接
	 * 使用不同的证书，这会很复杂。所以我们现在为每个连接
	 * 创建一个单独的上下文，并接受额外开销。
	 */
#ifdef FDDGM
    if(g_tlcp_enable)
    {
    	fc_SSL_context = SSL_CTX_new(TLCP_client_method());
    }
	else
#endif
	fc_SSL_context = SSL_CTX_new(SSLv23_method());
	if (!fc_SSL_context)
	{
		char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("could not create SSL context: %s\n"),
						  fc_err);
		fc_SSLerrfree(fc_err);
		return -1;
	}

	/*
	 * 如果定义了libpq包装回调，则将客户端证书密码提示
	 * 委托给该回调。
	 *
	 * 如果应用程序没有安装自己的回调，并且sslpassword
	 * 参数非空，我们现在安装自己的回调，以确保我们将
	 * PGconn->sslpassword提供给OpenSSL，而不是让它在stdin上
	 * 提示。
	 *
	 * 这将替换OpenSSL的默认PEM_def_callback（在stdin上提示），
	 * 但我们仅为此SSL上下文设置它，因此这是无害的。
	 */
	if (PQsslKeyPassHook
		|| (fc_conn->sslpassword && strlen(fc_conn->sslpassword) > 0))
	{
		SSL_CTX_set_default_passwd_cb(fc_SSL_context, fc_PQssl_passwd_cb);
		SSL_CTX_set_default_passwd_cb_userdata(fc_SSL_context, fc_conn);
	}

	/* 禁用旧的协议版本 */
	SSL_CTX_set_options(fc_SSL_context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);

	/* 如果必要，设置最小和最大协议版本 */
	if (fc_conn->ssl_min_protocol_version &&
		strlen(fc_conn->ssl_min_protocol_version) != 0)
	{
		int			fc_ssl_min_ver;

		fc_ssl_min_ver = fc_ssl_protocol_version_to_openssl(fc_conn->ssl_min_protocol_version);

		if (fc_ssl_min_ver == -1)
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("invalid value \"%s\" for minimum SSL protocol version\n"),
							  fc_conn->ssl_min_protocol_version);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}

		if (!SSL_CTX_set_min_proto_version(fc_SSL_context, fc_ssl_min_ver))
		{
			char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not set minimum SSL protocol version: %s\n"),
							  fc_err);
			fc_SSLerrfree(fc_err);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}
	}

	if (fc_conn->ssl_max_protocol_version &&
		strlen(fc_conn->ssl_max_protocol_version) != 0)
	{
		int			fc_ssl_max_ver;

		fc_ssl_max_ver = fc_ssl_protocol_version_to_openssl(fc_conn->ssl_max_protocol_version);

		if (fc_ssl_max_ver == -1)
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("invalid value \"%s\" for maximum SSL protocol version\n"),
							  fc_conn->ssl_max_protocol_version);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}

		if (!SSL_CTX_set_max_proto_version(fc_SSL_context, fc_ssl_max_ver))
		{
			char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not set maximum SSL protocol version: %s\n"),
							  fc_err);
			fc_SSLerrfree(fc_err);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}
	}

	/*
	 * 禁用 OpenSSL 的移动写缓冲区完整性检查，因为它会导致
	 * 在非阻塞发送情况下的不必要失败。
	 */
	SSL_CTX_set_mode(fc_SSL_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);

	/*
	 * 如果根证书文件存在，加载它以便进行证书
	 * 验证。如果 sslmode 是 "verify-full"，我们将在连接完成后进行进一步
	 * 验证。
	 */
	if (fc_conn->sslrootcert && strlen(fc_conn->sslrootcert) > 0)
		strlcpy(fc_fnbuf, fc_conn->sslrootcert, sizeof(fc_fnbuf));
	else if (fc_have_homedir)
		snprintf(fc_fnbuf, sizeof(fc_fnbuf), "%s/%s", fc_homedir, ROOT_CERT_FILE);
	else
		fc_fnbuf[0] = '\0';

	if (fc_fnbuf[0] != '\0' &&
		stat(fc_fnbuf, &fc_buf) == 0)
	{
		X509_STORE *fc_cvstore;

		if (SSL_CTX_load_verify_locations(fc_SSL_context, fc_fnbuf, NULL) != 1)
		{
			char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not read root certificate file \"%s\": %s\n"),
							  fc_fnbuf, fc_err);
			fc_SSLerrfree(fc_err);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}

		if ((fc_cvstore = SSL_CTX_get_cert_store(fc_SSL_context)) != NULL)
		{
			char	   *fc_fname = NULL;
			char	   *fc_dname = NULL;

			if (fc_conn->sslcrl && strlen(fc_conn->sslcrl) > 0)
				fc_fname = fc_conn->sslcrl;
			if (fc_conn->sslcrldir && strlen(fc_conn->sslcrldir) > 0)
				fc_dname = fc_conn->sslcrldir;

			/* 默认使用默认的 CRL 文件 */
			if (!fc_fname && !fc_dname && fc_have_homedir)
			{
				snprintf(fc_fnbuf, sizeof(fc_fnbuf), "%s/%s", fc_homedir, ROOT_CRL_FILE);
				fc_fname = fc_fnbuf;
			}

			/* 设置标志以检查完整的 CRL 链 */
			if ((fc_fname || fc_dname) &&
				X509_STORE_load_locations(fc_cvstore, fc_fname, fc_dname) == 1)
			{
				X509_STORE_set_flags(fc_cvstore,
									 X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
			}

			/* 如果未找到，静默忽略；我们不要求 CRL */
			ERR_clear_error();
		}
		fc_have_rootcert = true;
	}
	else
	{
		/*
		 * stat() 失败；假设根文件不存在。如果 sslmode 是
		 * verify-ca 或 verify-full，这是一个错误。否则，继续
		 * 而不进行任何服务器证书验证。
		 */
		if (fc_conn->sslmode[0] == 'v')	/* "verify-ca" 或 "verify-full" */
		{
			/*
			 * 唯一通过空文件名到达此处的方式是
			 * pqGetHomeDirectory 失败。这是一个足够不寻常的情况，
			 * 看起来值得为此提供一个专门的错误信息。
			 */
			if (fc_fnbuf[0] == '\0')
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not get home directory to locate root certificate file\n"
												"Either provide the file or change sslmode to disable server certificate verification.\n"));
			else
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("root certificate file \"%s\" does not exist\n"
												"Either provide the file or change sslmode to disable server certificate verification.\n"), fc_fnbuf);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}
		fc_have_rootcert = false;
	}

#ifdef FDDGM
	if(g_tlcp_enable)
	{
		//加载签名证书文件
		int fc_ret = fc_load_certificate_file(fc_conn, fc_have_homedir, fc_homedir, USER_CERT_FILE, fc_SSL_context, SSL_USAGE_SIG, &fc_have_cert);
		if(0 != fc_ret)
		{
			return fc_ret;
		}
		//加载加密证书文件
		fc_ret = fc_load_certificate_file(fc_conn, fc_have_homedir, fc_homedir, USER_ENC_CERT_FILE, fc_SSL_context, SSL_USAGE_ENC, &fc_have_enccert);
		if(0 != fc_ret)
		{
			return fc_ret;
		}
    }
	else
	{
#endif
	/* 读取客户端证书文件 */
	if (fc_conn->sslcert && strlen(fc_conn->sslcert) > 0)
		strlcpy(fc_fnbuf, fc_conn->sslcert, sizeof(fc_fnbuf));
	else if (fc_have_homedir)
		snprintf(fc_fnbuf, sizeof(fc_fnbuf), "%s/%s", fc_homedir, USER_CERT_FILE);
	else
		fc_fnbuf[0] = '\0';

	if (fc_fnbuf[0] == '\0')
	{
		/* 没有家目录，继续没有客户端证书 */
		fc_have_cert = false;
	}
	else if (stat(fc_fnbuf, &fc_buf) != 0)
	{
		/*
		 * 如果文件不存在，就继续没有客户端证书；服务器
		 * 可能会接受连接，也可能不会。然而，任何其他错误
		 * 都是投诉的理由。
		 */
		if (errno != ENOENT && errno != ENOTDIR)
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not open certificate file \"%s\": %s\n"),
							  fc_fnbuf, strerror_r(errno, fc_sebuf, sizeof(fc_sebuf)));
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}
		fc_have_cert = false;
	}
	else
	{
		/*
		 * 证书文件存在，因此加载它。由于 OpenSSL 不提供
		 * "SSL_use_certificate_chain_file" 的等效方法，我们必须将其加载
		 * 到 SSL 上下文中，而不是 SSL 对象中。
		 */
		if (SSL_CTX_use_certificate_chain_file(fc_SSL_context, fc_fnbuf) != 1)
		{
			char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("could not read certificate file \"%s\": %s\n"),
							  fc_fnbuf, fc_err);
			fc_SSLerrfree(fc_err);
			SSL_CTX_free(fc_SSL_context);
			return -1;
		}

		/* 需要加载相关的私钥 */
		fc_have_cert = true;
	}
#ifdef FDDGM
	}
#endif

	/*
	 * SSL 上下文现在已经加载了正确的根证书和客户端
	 * 证书。创建一个特定于连接的 SSL 对象。私钥
	 * 直接加载到 SSL 对象中。（我们也可以将私钥
	 * 加载到上下文中，但从历史上看我们一直是这样做的，
	 * 而且这实际上并不重要。）
	 */
	if (!(fc_conn->ssl = SSL_new(fc_SSL_context)) ||
		!SSL_set_app_data(fc_conn->ssl, fc_conn) ||
		!fc_my_SSL_set_fd(fc_conn, fc_conn->sock))
	{
		char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("could not establish SSL connection: %s\n"),
						  fc_err);
		fc_SSLerrfree(fc_err);
		SSL_CTX_free(fc_SSL_context);
		return -1;
	}
	fc_conn->ssl_in_use = true;

	/*
	 * SSL 上下文由 OpenSSL 进行引用计数。我们可以在
	 * 创建了 SSL 对象后立即释放它，它会一直存在于
	 * 需要的时候。
	 */
	SSL_CTX_free(fc_SSL_context);
	fc_SSL_context = NULL;

	/*
	 * 如果连接参数启用了服务器名称指示 (SNI)，则设置 SNI。
	 * 根据 RFC 6066，如果主机是字面 IP 地址（IPv4
	 * 或 IPv6），则不要设置它。
	 */
	if (fc_conn->sslsni && fc_conn->sslsni[0] == '1')
	{
		const char *fc_host = fc_conn->connhost[fc_conn->whichhost].host;

		if (fc_host && fc_host[0] &&
			!(strspn(fc_host, "0123456789.") == strlen(fc_host) ||
			  strchr(fc_host, ':')))
		{
			if (SSL_set_tlsext_host_name(fc_conn->ssl, fc_host) != 1)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not set SSL Server Name Indication (SNI): %s\n"),
								  fc_err);
				fc_SSLerrfree(fc_err);
				return -1;
			}
		}
	}
#ifdef FDDGM
	if(g_tlcp_enable)
	{
		//加载签名证书私钥
		int fc_ret = fc_load_privatekey_file(fc_conn, fc_have_homedir, fc_homedir, USER_KEY_FILE, SSL_USAGE_SIG, fc_have_cert);
		if(0 != fc_ret)
		{
			return fc_ret;
		}
		//加载加密证书私钥
		fc_ret = fc_load_privatekey_file(fc_conn, fc_have_homedir, fc_homedir, USER_ENC_KEY_FILE, SSL_USAGE_ENC, fc_have_enccert);
		if(0 != fc_ret)
		{
			return fc_ret;
		}
	}
	else
	{
#endif
	/*
	 * 读取 SSL 密钥。如果指定了密钥，如果存在冒号，则将其视为 engine:key
	 * 组合 - 我们不支持名称中包含冒号的文件。例外是如果第二个字符是冒号，
	 * 在这种情况下它可以是带有驱动器说明的 Windows 文件名。
	 */
	if (fc_have_cert && fc_conn->sslkey && strlen(fc_conn->sslkey) > 0)
	{
#ifdef USE_SSL_ENGINE
		if (strchr(fc_conn->sslkey, ':')
#ifdef WIN32
			&& fc_conn->sslkey[1] != ':'
#endif
			)
		{
			/* 冒号，但不是在第二个字符中，视为 engine:key */
			char	   *fc_engine_str = strdup(fc_conn->sslkey);
			char	   *fc_engine_colon;
			EVP_PKEY   *fc_pkey;

			if (fc_engine_str == NULL)
			{
				appendPQExpBufferStr(&fc_conn->errorMessage,
									 libpq_gettext("out of memory\n"));
				return -1;
			}

			/* 不能返回 NULL，因为我们已经在 strdup 之前检查过 */
			fc_engine_colon = strchr(fc_engine_str, ':');

			*fc_engine_colon = '\0';	/* engine_str 现在有引擎名称 */
			fc_engine_colon++;		/* engine_colon 现在有密钥名称 */

			fc_conn->engine = ENGINE_by_id(fc_engine_str);
			if (fc_conn->engine == NULL)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not load SSL engine \"%s\": %s\n"),
								  fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				free(fc_engine_str);
				return -1;
			}

			if (ENGINE_init(fc_conn->engine) == 0)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not initialize SSL engine \"%s\": %s\n"),
								  fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				ENGINE_free(fc_conn->engine);
				fc_conn->engine = NULL;
				free(fc_engine_str);
				return -1;
			}

			fc_pkey = ENGINE_load_private_key(fc_conn->engine, fc_engine_colon,
										   NULL, NULL);
			if (fc_pkey == NULL)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not read private SSL key \"%s\" from engine \"%s\": %s\n"),
								  fc_engine_colon, fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				ENGINE_finish(fc_conn->engine);
				ENGINE_free(fc_conn->engine);
				fc_conn->engine = NULL;
				free(fc_engine_str);
				return -1;
			}
			if (SSL_use_PrivateKey(fc_conn->ssl, fc_pkey) != 1)
			{
				char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not load private SSL key \"%s\" from engine \"%s\": %s\n"),
								  fc_engine_colon, fc_engine_str, fc_err);
				fc_SSLerrfree(fc_err);
				ENGINE_finish(fc_conn->engine);
				ENGINE_free(fc_conn->engine);
				fc_conn->engine = NULL;
				free(fc_engine_str);
				return -1;
			}

			free(fc_engine_str);

			fc_fnbuf[0] = '\0';	/* 表示我们不打算从一个
								 * 文件中加载 */
		}
		else
#endif							/* USE_SSL_ENGINE */
		{
			/* PGSSLKEY 不是一个引擎，将其视为文件名 */
			strlcpy(fc_fnbuf, fc_conn->sslkey, sizeof(fc_fnbuf));
		}
	}
	else if (fc_have_homedir)
	{
		/* 未指定 PGSSLKEY，加载默认文件 */
		snprintf(fc_fnbuf, sizeof(fc_fnbuf), "%s/%s", fc_homedir, USER_KEY_FILE);
	}
	else
		fc_fnbuf[0] = '\0';

	if (fc_have_cert && fc_fnbuf[0] != '\0')
	{
		/* 从文件读取客户端密钥 */

		if (stat(fc_fnbuf, &fc_buf) != 0)
		{
			if (errno == ENOENT)
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("certificate present, but not private key file \"%s\"\n"),
								  fc_fnbuf);
			else
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not stat private key file \"%s\": %m\n"),
								  fc_fnbuf);
			return -1;
		}

		/* 密钥文件必须是常规文件 */
		if (!S_ISREG(fc_buf.st_mode))
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("private key file \"%s\" is not a regular file\n"),
							  fc_fnbuf);
			return -1;
		}

		/*
		 * 拒绝加载所有人可读的密钥文件。我们接受根拥有
		 * 的文件，模式为0640或更小，以便我们能够访问系统范围的
		 * 证书，前提是我们拥有允许我们读取它们的附加组成员资格。
		 * 对于非根拥有的文件，需要模式为0600或更小。我们不需要
		 * 精确检查文件的拥有权；如果我们能够读取文件，尽管它
		 * 拥有如此限制的权限，它一定具有正确的拥有权。
		 *
		 * 注意：在收紧这些规则时要非常小心。一些人
		 * 期望，例如，作为根用户运行的客户端进程应该能够使用
		 * 非根拥有的密钥文件。
		 *
		 * 请注意，在
		 * src/backend/libpq/be-secure-common.c 中执行了大致类似的检查，
		 * 因此在这里的任何更改也可能需要在那里进行。然而，这段代码
		 * 处理当前用户 == root 的情况，而那段代码没有。
		 *
		 * 理想情况下，我们将在Windows上执行类似的权限检查，
		 * 但目前尚不清楚这将如何工作，因为Unix风格的权限可能
		 * 不可用。
		 */
#if !defined(WIN32) && !defined(__CYGWIN__)
		if (fc_buf.st_uid == 0 ?
			fc_buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO) :
			fc_buf.st_mode & (S_IRWXG | S_IRWXO))
		{
			appendPQExpBuffer(&fc_conn->errorMessage,
							  libpq_gettext("private key file \"%s\" has group or world access; file must have permissions u=rw (0600) or less if owned by the current user, or permissions u=rw,g=r (0640) or less if owned by root\n"),
							  fc_fnbuf);
			return -1;
		}
#endif

		if (SSL_use_PrivateKey_file(fc_conn->ssl, fc_fnbuf, SSL_FILETYPE_PEM) != 1)
		{
			char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

			/*
			 * 我们将尝试以DER（二进制ASN.1）格式加载文件，如果
			 * 这也失败了，则报告原始错误。这可能会掩盖
			 * DER格式证书存在问题的情况，但我们必须复制
			 * openssl的格式检测，以比这更智能。我们不能仅仅
			 * 通过探测前导-----BEGIN，因为PEM可以具有前导的不匹配
			 * 行和空白。OpenSSL不公开其get_name(...)，并且它的
			 * PEM例程在失败模式上没有足够的区分度，让我们能够
			 * 区分“不是PEM，尝试DER”和“密码错误”。
			 */
			if (SSL_use_PrivateKey_file(fc_conn->ssl, fc_fnbuf, SSL_FILETYPE_ASN1) != 1)
			{
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("could not load private key file \"%s\": %s\n"),
								  fc_fnbuf, fc_err);
				fc_SSLerrfree(fc_err);
				return -1;
			}

			fc_SSLerrfree(fc_err);
		}
	}

	/* 验证证书和密钥是否匹配 */
	if (fc_have_cert &&
		SSL_check_private_key(fc_conn->ssl) != 1)
	{
		char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("certificate does not match private key file \"%s\": %s\n"),
						  fc_fnbuf, fc_err);
		fc_SSLerrfree(fc_err);
		return -1;
	}
#ifdef FDDGM
	}
#endif

	/*
	 * 如果加载了根证书，还要设置我们的证书验证回调。
	 */
	if (fc_have_rootcert)
		SSL_set_verify(fc_conn->ssl, SSL_VERIFY_PEER, fc_verify_cb);

	/*
	 * 如有必要，设置压缩选项。
	 */
	if (fc_conn->sslcompression && fc_conn->sslcompression[0] == '0')
		SSL_set_options(fc_conn->ssl, SSL_OP_NO_COMPRESSION);
	else
		SSL_clear_options(fc_conn->ssl, SSL_OP_NO_COMPRESSION);

	return 0;
}

/*
 * 尝试协商 SSL 连接。
 */
static PostgresPollingStatusType
fc_open_client_SSL(PGconn *fc_conn)
{
	int			fc_r;

	SOCK_ERRNO_SET(0);
	ERR_clear_error();
	fc_r = SSL_connect(fc_conn->ssl);
	if (fc_r <= 0)
	{
		int			fc_save_errno = SOCK_ERRNO;
		int			fc_err = SSL_get_error(fc_conn->ssl, fc_r);
		unsigned long fc_ecode;

		fc_ecode = ERR_get_error();
		switch (fc_err)
		{
			case SSL_ERROR_WANT_READ:
				return PGRES_POLLING_READING;

			case SSL_ERROR_WANT_WRITE:
				return PGRES_POLLING_WRITING;

			case SSL_ERROR_SYSCALL:
				{
					char		fc_sebuf[PG_STRERROR_R_BUFLEN];

					if (fc_r == -1 && fc_save_errno != 0)
						appendPQExpBuffer(&fc_conn->errorMessage,
										  libpq_gettext("SSL SYSCALL error: %s\n"),
										  SOCK_STRERROR(fc_save_errno, fc_sebuf, sizeof(fc_sebuf)));
					else
						appendPQExpBufferStr(&fc_conn->errorMessage,
											 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
					pgtls_close(fc_conn);
					return PGRES_POLLING_FAILED;
				}
			case SSL_ERROR_SSL:
				{
					char	   *fc_err = fc_SSLerrmessage(fc_ecode);

					appendPQExpBuffer(&fc_conn->errorMessage,
									  libpq_gettext("SSL error: %s\n"),
									  fc_err);
					fc_SSLerrfree(fc_err);
					switch (ERR_GET_REASON(fc_ecode))
					{
							/*
							 * UNSUPPORTED_PROTOCOL、WRONG_VERSION_NUMBER 和
							 * TLSV1_ALERT_PROTOCOL_VERSION 已被观察到
							 * 在尝试与旧的 OpenSSL 库通信时，或者当客户端和
							 * 服务器指定不相交的协议范围时。
							 * NO_PROTOCOLS_AVAILABLE 在本地配置错误时发生
							 * （尽管我们的检查，如果 openssl.cnf 注入了
							 * 我们没有考虑的限制，也可能发生）。目前不清楚
							 * 什么情况会导致 OpenSSL 返回这里列出的其他代码，
							 * 但有关协议版本的提示似乎对所有情况都适用。
							 */
						case SSL_R_NO_PROTOCOLS_AVAILABLE:
						case SSL_R_UNSUPPORTED_PROTOCOL:
						case SSL_R_BAD_PROTOCOL_VERSION_NUMBER:
						case SSL_R_UNKNOWN_PROTOCOL:
						case SSL_R_UNKNOWN_SSL_VERSION:
						case SSL_R_UNSUPPORTED_SSL_VERSION:
						case SSL_R_WRONG_SSL_VERSION:
						case SSL_R_WRONG_VERSION_NUMBER:
						case SSL_R_TLSV1_ALERT_PROTOCOL_VERSION:
#ifdef SSL_R_VERSION_TOO_HIGH
						case SSL_R_VERSION_TOO_HIGH:
						case SSL_R_VERSION_TOO_LOW:
#endif
							appendPQExpBuffer(&fc_conn->errorMessage,
											  libpq_gettext("This may indicate that the server does not support any SSL protocol version between %s and %s.\n"),
											  fc_conn->ssl_min_protocol_version ?
											  fc_conn->ssl_min_protocol_version :
											  MIN_OPENSSL_TLS_VERSION,
											  fc_conn->ssl_max_protocol_version ?
											  fc_conn->ssl_max_protocol_version :
											  MAX_OPENSSL_TLS_VERSION);
							break;
						default:
							break;
					}
					pgtls_close(fc_conn);
					return PGRES_POLLING_FAILED;
				}

			default:
				appendPQExpBuffer(&fc_conn->errorMessage,
								  libpq_gettext("unrecognized SSL error code: %d\n"),
								  fc_err);
				pgtls_close(fc_conn);
				return PGRES_POLLING_FAILED;
		}
	}

	/*
	 * 如果 root.crt 存在，我们已经在 initialize_SSL() 中使用
	 * SSL_CTX_set_verify() 检查了服务器证书。
	 */

	/* 获取服务器证书 */
	fc_conn->peer = SSL_get_peer_certificate(fc_conn->ssl);
	if (fc_conn->peer == NULL)
	{
		char	   *fc_err = fc_SSLerrmessage(ERR_get_error());

		appendPQExpBuffer(&fc_conn->errorMessage,
						  libpq_gettext("certificate could not be obtained: %s\n"),
						  fc_err);
		fc_SSLerrfree(fc_err);
		pgtls_close(fc_conn);
		return PGRES_POLLING_FAILED;
	}

	if (!pq_verify_peer_name_matches_certificate(fc_conn))
	{
		pgtls_close(fc_conn);
		return PGRES_POLLING_FAILED;
	}

	/* SSL 握手已完成 */
	return PGRES_POLLING_OK;
}

void pgtls_close(PGconn *fc_conn)
{
	bool		fc_destroy_needed = false;

	if (fc_conn->ssl_in_use)
	{
		if (fc_conn->ssl)
		{
			/*
			 * 由于可能后续调用 OpenSSL 例程需要我们的线程回调，
			 * 我们不能在这里销毁所有与 SSL 相关的内容，因此在这里设置一个标志，
			 * 并在最后检查。
			 */

			SSL_shutdown(fc_conn->ssl);
			SSL_free(fc_conn->ssl);
			fc_conn->ssl = NULL;
			fc_conn->ssl_in_use = false;

			fc_destroy_needed = true;
		}

		if (fc_conn->peer)
		{
			X509_free(fc_conn->peer);
			fc_conn->peer = NULL;
		}

#ifdef USE_SSL_ENGINE
		if (fc_conn->engine)
		{
			ENGINE_finish(fc_conn->engine);
			ENGINE_free(fc_conn->engine);
			fc_conn->engine = NULL;
		}
#endif
	}
	else
	{
		/*
		 * 在非 SSL 的情况下，如果连接已经加载，则只需移除加密回调。
		 * 该代码路径不依赖于任何待处理的 SSL 调用。
		 */
		if (fc_conn->crypto_loaded)
			fc_destroy_needed = true;
	}

	/*
	 * 如果这是最后一个使用 libcrypto 的连接，这将移除我们的加密锁定钩子，
	 * 这意味着我们必须等到所有潜在的 SSL 调用发生后再调用它，
	 * 否则我们可能会遇到竞争条件和死锁。
	 *
	 * 请参见 destroy_ssl_system() 之上的注释。
	 */
	if (fc_destroy_needed)
	{
		fc_destroy_ssl_system();
		fc_conn->crypto_loaded = false;
	}
}


/*
 * Obtain reason string for passed SSL errcode
 *
 * ERR_get_error() is used by caller to get errcode to pass here.
 * The result must be freed after use, using fc_SSLerrfree.
 *
 * Some caution is needed here since ERR_reason_error_string will return NULL
 * if it doesn't recognize the error code, or (in OpenSSL >= 3) if the code
 * represents a system errno value.  We don't want to return NULL ever.
 */
static char ssl_nomem[] = "out of memory allocating error description";

#define SSL_ERR_LEN 128

static char * fc_SSLerrmessage(unsigned long fc_ecode)
{
	const char *fc_errreason;
	char	   *fc_errbuf;

	fc_errbuf = malloc(SSL_ERR_LEN);
	if (!fc_errbuf)
		return ssl_nomem;
	if (fc_ecode == 0)
	{
		snprintf(fc_errbuf, SSL_ERR_LEN, libpq_gettext("no SSL error reported"));
		return fc_errbuf;
	}
	fc_errreason = ERR_reason_error_string(fc_ecode);
	if (fc_errreason != NULL)
	{
		strlcpy(fc_errbuf, fc_errreason, SSL_ERR_LEN);
		return fc_errbuf;
	}

	/*
	 * 在 OpenSSL 3.0.0 及以后的版本中，ERR_reason_error_string 不再映射系统
	 * errno 值。（请参阅 OpenSSL 源代码以了解解释。）
	 * 我们可以用这段代码来弥补这个缺陷。较旧的 OpenSSL
	 * 版本没有 ERR_SYSTEM_ERROR 宏，但这没关系，因为
	 * 它们也没有这个缺陷。
	 */
#ifdef ERR_SYSTEM_ERROR
	if (ERR_SYSTEM_ERROR(fc_ecode))
	{
		strerror_r(ERR_GET_REASON(fc_ecode), fc_errbuf, SSL_ERR_LEN);
		return fc_errbuf;
	}
#endif

	/* 别无选择，只能报告数值错误代码 */
	snprintf(fc_errbuf, SSL_ERR_LEN, libpq_gettext("SSL error code %lu"), fc_ecode);
	return fc_errbuf;
}

static void fc_SSLerrfree(char *fc_buf)
{
	if (fc_buf != ssl_nomem)
		free(fc_buf);
}

/* ------------------------------------------------------------ */
/*					SSL 信息函数					*/
/* ------------------------------------------------------------ */

/*
 * 返回指向 OpenSSL 对象的指针。
 */
void * PQgetssl(PGconn *fc_conn)
{
	if (!fc_conn)
		return NULL;
	return fc_conn->ssl;
}

void * PQsslStruct(PGconn *fc_conn, const char *fc_struct_name)
{
	if (!fc_conn)
		return NULL;
	if (strcmp(fc_struct_name, "OpenSSL") == 0)
		return fc_conn->ssl;
	return NULL;
}

const char *const *
PQsslAttributeNames(PGconn *fc_conn)
{
	static const char *const fc_result[] = {
		"library",
		"key_bits",
		"cipher",
		"compression",
		"protocol",
		NULL
	};

	return fc_result;
}

const char * PQsslAttribute(PGconn *fc_conn, const char *fc_attribute_name)
{
	if (!fc_conn)
	{
		/* PQsslAttribute(NULL, "library") 报告默认的 SSL 库 */
		if (strcmp(fc_attribute_name, "library") == 0)
			return "OpenSSL";
		return NULL;
	}

	/* 对于未加密的连接，所有属性均读取为 NULL */
	if (fc_conn->ssl == NULL)
		return NULL;

	if (strcmp(fc_attribute_name, "library") == 0)
		return "OpenSSL";

	if (strcmp(fc_attribute_name, "key_bits") == 0)
	{
		static char fc_sslbits_str[12];
		int			fc_sslbits;

		SSL_get_cipher_bits(fc_conn->ssl, &fc_sslbits);
		snprintf(fc_sslbits_str, sizeof(fc_sslbits_str), "%d", fc_sslbits);
		return fc_sslbits_str;
	}

	if (strcmp(fc_attribute_name, "cipher") == 0)
		return SSL_get_cipher(fc_conn->ssl);

	if (strcmp(fc_attribute_name, "compression") == 0)
		return SSL_get_current_compression(fc_conn->ssl) ? "on" : "off";

	if (strcmp(fc_attribute_name, "protocol") == 0)
		return SSL_get_version(fc_conn->ssl);

	return NULL;				/* 未知属性 */
}

/*
 * 私有替代 BIO：这使用 pqsecure_raw_write() 和 pqsecure_raw_read()
 * 进行发送和接收，以允许这些函数禁用 SIGPIPE 并在 I/O 错误时提供更好的错误消息。
 *
 * 这些函数与 OpenSSL 中的标准套接字 BIO 密切模仿；
 * 请参见 OpenSSL 的 crypto/bio/bss_sock.c 中的 sock_read() 和 sock_write()。
 * XXX OpenSSL 1.0.1e 考虑到的重试原因代码比 EINTR 多得多；
 * 我们需要采纳他们的逻辑吗？
 */

/* 受 ssl_config_mutex 保护 */
static BIO_METHOD *my_bio_methods;

static int fc_my_sock_read(BIO *fc_h, char *fc_buf, int fc_size)
{
	int			fc_res;

	fc_res = pqsecure_raw_read((PGconn *) BIO_get_app_data(fc_h), fc_buf, fc_size);
	BIO_clear_retry_flags(fc_h);
	if (fc_res < 0)
	{
		/* 如果我们被中断，告知调用者重试 */
		switch (SOCK_ERRNO)
		{
#ifdef EAGAIN
			case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
#endif
			case EINTR:
				BIO_set_retry_read(fc_h);
				break;

			default:
				break;
		}
	}

	return fc_res;
}

static int fc_my_sock_write(BIO *fc_h, const char *fc_buf, int fc_size)
{
	int			fc_res;

	fc_res = pqsecure_raw_write((PGconn *) BIO_get_app_data(fc_h), fc_buf, fc_size);
	BIO_clear_retry_flags(fc_h);
	if (fc_res < 0)
	{
		/* 如果我们被中断，告知调用者重试 */
		switch (SOCK_ERRNO)
		{
#ifdef EAGAIN
			case EAGAIN:
#endif
#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
			case EWOULDBLOCK:
#endif
			case EINTR:
				BIO_set_retry_write(fc_h);
				break;

			default:
				break;
		}
	}

	return fc_res;
}

static BIO_METHOD *
fc_my_BIO_s_socket(void)
{
	BIO_METHOD *fc_res;

#ifdef ENABLE_THREAD_SAFETY
	if (pthread_mutex_lock(&ssl_config_mutex))
		return NULL;
#endif

	fc_res = my_bio_methods;

	if (!my_bio_methods)
	{
		BIO_METHOD *fc_biom = (BIO_METHOD *) BIO_s_socket();
#ifdef HAVE_BIO_METH_NEW
		int			fc_my_bio_index;

		fc_my_bio_index = BIO_get_new_index();
		if (fc_my_bio_index == -1)
			goto err;
		fc_my_bio_index |= (BIO_TYPE_DESCRIPTOR | BIO_TYPE_SOURCE_SINK);
		fc_res = BIO_meth_new(fc_my_bio_index, "libpq socket");
		if (!fc_res)
			goto err;

		/*
		 * 在撰写本文时，这些函数从不失败。 但还是检查一下，
		 * 就像 OpenSSL 自己的示例那样。
		 */
		if (!BIO_meth_set_write(fc_res, fc_my_sock_write) ||
			!BIO_meth_set_read(fc_res, fc_my_sock_read) ||
			!BIO_meth_set_gets(fc_res, BIO_meth_get_gets(fc_biom)) ||
			!BIO_meth_set_puts(fc_res, BIO_meth_get_puts(fc_biom)) ||
			!BIO_meth_set_ctrl(fc_res, BIO_meth_get_ctrl(fc_biom)) ||
			!BIO_meth_set_create(fc_res, BIO_meth_get_create(fc_biom)) ||
			!BIO_meth_set_destroy(fc_res, BIO_meth_get_destroy(fc_biom)) ||
			!BIO_meth_set_callback_ctrl(fc_res, BIO_meth_get_callback_ctrl(fc_biom)))
		{
			goto err;
		}
#else
		fc_res = malloc(sizeof(BIO_METHOD));
		if (!fc_res)
			goto err;
		memcpy(fc_res, biom, sizeof(BIO_METHOD));
		fc_res->bread = fc_my_sock_read;
		fc_res->bwrite = fc_my_sock_write;
#endif
	}

	my_bio_methods = fc_res;

#ifdef ENABLE_THREAD_SAFETY
	pthread_mutex_unlock(&ssl_config_mutex);
#endif

	return fc_res;

err:
#ifdef HAVE_BIO_METH_NEW
	if (fc_res)
		BIO_meth_free(fc_res);
#else
	if (fc_res)
		free(fc_res);
#endif

#ifdef ENABLE_THREAD_SAFETY
	pthread_mutex_unlock(&ssl_config_mutex);
#endif
	return NULL;
}

/* 这应该与 OpenSSL 的 SSL_set_fd 完全匹配，除了使用我的 BIO */
static int fc_my_SSL_set_fd(PGconn *fc_conn, int fc_fd)
{
	int			fc_ret = 0;
	BIO		   *fc_bio;
	BIO_METHOD *fc_bio_method;

	fc_bio_method = fc_my_BIO_s_socket();
	if (fc_bio_method == NULL)
	{
		SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
		goto err;
	}
	fc_bio = BIO_new(fc_bio_method);
	if (fc_bio == NULL)
	{
		SSLerr(SSL_F_SSL_SET_FD, ERR_R_BUF_LIB);
		goto err;
	}
	BIO_set_app_data(fc_bio, fc_conn);

	SSL_set_bio(fc_conn->ssl, fc_bio, fc_bio);
	BIO_set_fd(fc_bio, fc_fd, BIO_NOCLOSE);
	fc_ret = 1;
err:
	return fc_ret;
}

/*
 * 这是默认的处理程序，用于从
 * conn->sslpassword 返回客户端证书密码。应用可以显式安装它，如果他们想要
 * 防止 openssl 在标准输入上提示。
 */
int PQdefaultSSLKeyPassHook_OpenSSL(char *fc_buf, int fc_size, PGconn *fc_conn)
{
	if (fc_conn && fc_conn->sslpassword)
	{
		if (strlen(fc_conn->sslpassword) + 1 > fc_size)
			fprintf(stderr, libpq_gettext("WARNING: sslpassword truncated\n"));
		strncpy(fc_buf, fc_conn->sslpassword, fc_size);
		fc_buf[fc_size - 1] = '\0';
		return strlen(fc_buf);
	}
	else
	{
		fc_buf[0] = '\0';
		return 0;
	}
}

PQsslKeyPassHook_OpenSSL_type
PQgetSSLKeyPassHook_OpenSSL(void)
{
	return PQsslKeyPassHook;
}

void PQsetSSLKeyPassHook_OpenSSL(PQsslKeyPassHook_OpenSSL_type fc_hook)
{
	PQsslKeyPassHook = fc_hook;
}

/*
 * 提供一个密码以解密客户端证书。
 *
 * 这必须与 OpenSSL 类型 pem_password_cb 匹配。
 */
static int fc_PQssl_passwd_cb(char *fc_buf, int fc_size, int fc_rwflag, void *fc_userdata)
{
	PGconn	   *fc_conn = fc_userdata;

	if (PQsslKeyPassHook)
		return PQsslKeyPassHook(fc_buf, fc_size, fc_conn);
	else
		return PQdefaultSSLKeyPassHook_OpenSSL(fc_buf, fc_size, fc_conn);
}

/*
 * 将 TLS 协议版本字符串转换为 OpenSSL 值
 *
 * 如果传递的版本不受当前 OpenSSL 版本支持，
 * 那么我们返回 -1。如果返回一个非负值，后续代码可以
 * 假设它正在使用一个受支持的版本。
 *
 * 注意：这与 be-secure-openssl.c 中的后端例程相当相似，
 * 所以如果更改这个，请确保同时更新两个例程。
 */
static int fc_ssl_protocol_version_to_openssl(const char *fc_protocol)
{
	if (pg_strcasecmp("TLSv1", fc_protocol) == 0)
		return TLS1_VERSION;

#ifdef TLS1_1_VERSION
	if (pg_strcasecmp("TLSv1.1", fc_protocol) == 0)
		return TLS1_1_VERSION;
#endif

#ifdef TLS1_2_VERSION
	if (pg_strcasecmp("TLSv1.2", fc_protocol) == 0)
		return TLS1_2_VERSION;
#endif

#ifdef TLS1_3_VERSION
	if (pg_strcasecmp("TLSv1.3", fc_protocol) == 0)
		return TLS1_3_VERSION;
#endif

	return -1;
}
